
require("terrain")

-- Map object code
-- Generates terrain using terrain.lua

local function generateMapChunk(map, x, y)
	local box = {
		(x-1)*map.chunkSize + 1,
		(y-1)*map.chunkSize + 1,
		(x  )*map.chunkSize    ,
		(y  )*map.chunkSize    ,
	}
	local ter = GenerateTerrain(map.lowerPosition, box, map.cubeWidth, map.cubeHeight, map.getHeight, map.getColor, map.printName)
	return ter
end
function LoadMapChunk(map, x, y)
	assert(not (map.chunks[x] and map.chunks[x][y]), "LoadMapChunk: Chunk is already loaded: "..x.." "..y)
	map.chunks[x] = map.chunks[x] or {}
	map.chunks[x][y] = generateMapChunk(map, x, y)
	BuildTerrain(map.chunks[x][y])
end
function UnloadMapChunk(map, x, y)
	--assert(map.chunks[x] and map.chunks[x][y], "UnloadMapChunk: Chunk is not loaded: "..x.." "..y)
	if map.chunks[x] and map.chunks[x][y] then
		DeleteTerrain(map.chunks[x][y])
		map.chunks[x][y] = nil
	end
end
function UpdateMapChunk(map, x, y)
	--assert(map.chunks[x] and map.chunks[x][y], "UpdateMapChunk: Chunk is not loaded")
	if map.chunks[x] and map.chunks[x][y] then
		local newChunk = generateMapChunk(map, x, y)
		local oldChunk = map.chunks[x][y]
		ReplaceTerrain(oldChunk, newChunk)
		map.chunks[x][y] = newChunk
	else
		LoadMapChunk(map, x, y)
	end
end
function UnloadAllMapChunks(map)
	local chunkCoords = {}
	for x, xChunks in pairs(map.chunks) do
		for y, chunk in pairs(xChunks) do
			table.insert(chunkCoords, {x, y})
		end
	end
	for _, v in ipairs(chunkCoords) do
		UnloadMapChunk(map, v[1], v[2])
	end
end

local function setMapDimensions(map, w, h)
	map.heightmapWidth  = w
	map.heightmapHeight = h
	map.xChunks = map.heightmapWidth  * map.scale.x / map.chunkSize
	map.yChunks = map.heightmapHeight * map.scale.y / map.chunkSize
	assert(map.xChunks%1==0 and map.yChunks%1==0, "Map dimensions after scaling are not divisible by chunk size")
end
local function getPixel(img, x, y, scale)
	local px = math.clamp(math.round(x/scale.x), 1, img.width )
	local py = math.clamp(math.round(y/scale.y), 1, img.height)
	return img.pixels[px][py]
end
function SetMapHeightmap(map, heightMapFn)
	check(map, "table") check(heightMapFn, "string")
	print("Reading png: "..heightMapFn)
	local heightMapImg = pngImage(heightMapFn) or error("Invalid height map file \""..heightMapFn.."\"")
	map.getHeight = function(x, y)
		return getPixel(heightMapImg, x, y, map.scale).R * map.scale.z
	end
	assert(not map.heightmapWidth or (map.heightmapWidth == heightMapImg.width and map.heightmapHeight == heightMapImg.height), "trying to load a heightmap of different size")
	setMapDimensions(map, heightMapImg.width, heightMapImg.height)
	print("Done")
end
function SetMapColormap(map, colorMapFn)
	check(map, "table")
	print("Reading png: "..colorMapFn)
	if type(colorMapFn)=="string" then
		local colorMapImg = pngImage(colorMapFn) or error("Invalid color map file \""..colorMapFn.."\"")
		assert(map.heightmapWidth, "must load heightmap before colormap")
		assert(colorMapImg.width == map.heightmapWidth and colorMapImg.height == map.heightmapHeight, "Color map and height map are different sizes")
		map.getColor = function(x, y)
			local pixel = getPixel(colorMapImg, x, y, map.scale)
			local color = { pixel.R/255, pixel.G/255, pixel.B/255, 1 }
			return FindClosestColor(color)
		end
		map.colorMapImg = colorMapImg
	elseif type(colorMapFn)=="number" then
		local color = colorMapFn
		map.getColor = function(x, y)
			return color
		end
		map.colorMapImg = nil
	else error("invalid color map type: "..type(colorMapFn).." (must be filename or number)") end
	print("Done")
end
local function readMapFile(fn)
	local fi = io.open(fn, "r")
	if not fi then error("Could not open \""..fn.."\"") end
	local txt = fi:read("*a")
	fi:close()
	txt = txt:gsub("\r\n$", "")
	return txt
end
local function getDataPixel(x, y, mapData, map)
	local px = math.clamp(math.round(x/map.scale.x), 1, map.heightmapWidth ) - 1
	local py = math.clamp(math.round(y/map.scale.y), 1, map.heightmapHeight) - 1
	local offset = (py*map.heightmapWidth + px)*6
	local str = mapData:sub(offset+1, offset+6)
	local num = decodeBase64(str)
	return num
end
function SetMapDataFile(map, width, height, mapFn)
	check(map, "table") check(width, "number") check(height, "number") check(mapFn, "string")
	local mapData = readMapFile(mapFn)
	local expectedSize = width*height*6
	assert(#mapData == expectedSize, "map data is wrong size (expected "..expectedSize.." but got "..(#mapData))
	setMapDimensions(map, width, height)
	map.getColor = function(x, y)
		local n = getDataPixel(x, y, mapData, map)
		local r, g, b = math.floor(n/256^3), math.floor(n/256^2)%256, math.floor(n/256)%256
		return FindClosestColor({r/255, g/255, b/255})
	end
	map.getHeight = function(x, y)
		local n = getDataPixel(x, y, mapData, map)
		local h = (n%256)*map.scale.z
		return h
	end
end
function CreateMap(lowerPosition, scale, cubeWidth, cubeHeight, printName, chunkSize)
	check(lowerPosition, "vector") check(scale, "vector") check(cubeWidth, "number") check(cubeHeight, "number") checkopt(printName, "string") checkopt(chunkSize, "number")
	printName = printName or "modter/brickTop"
	chunkSize = chunkSize or 16
	
	-- map object
	local map = {
		scale = scale,
		cubeWidth = cubeWidth,
		cubeHeight = cubeHeight,
		printName = printName,
		chunkSize = chunkSize,
		lowerPosition = lowerPosition,
		chunks = {},
	}
	return map
end
